#include "script/glue/LuaGlue.h"

#include "gameobjects/GameObject.h"
#include "App.h"

namespace LuaGlue
{

	/**
	 * \brief Creates a GameObject in script.
	 * If called with a table as argument, will add components as necessary,
	 * according to the table's fields.
	 * First example is a simple GameObject:
	 * <code>go=GameObject.new()</code>
	 * Second example, creates a named GameObject with both a SceneNode and Entity via the TransformComponent and the RenderComponent:
	 * <code>sinbad=GameObject.new({ name="Sinbad", mesh="Sinbad.mesh" })</code>
	 * Third example, will also add a PhysicsComponent:
	 * <code>go=GameObject.new({mesh="Cube.mesh", shape="box", mass=10})</code>
	 */
	int GameObject::create(lua_State *L)
	{
		::GameObject *go = NULL;

		::App* app = ::App::getSingletonPtr();
		::Object params;

		params.addProperty<Ogre::SceneManager*>("scenemanager", app->getSceneManager());

		int nargs = lua_gettop(L);
		switch(nargs)
			{
			case 0:
				go = new ::GameObject();
				break;
			case 1:
				if(lua_istable(L, 1))
					{
						lua_pushnil(L);
						while (lua_next(L, 1) != 0)
							{
								const char* key = luaL_checkstring(L, -2);
								std::string typeName;
								switch(lua_type(L, -1))
									{
									case LUA_TBOOLEAN:
										params.addProperty<bool>(key, false);
										break;
									case LUA_TNUMBER:
										params.addProperty<float>(key, luaL_checknumber(L, -1));
										break;
									case LUA_TSTRING:
										params.addProperty<const char*>(key, luaL_checkstring(L, -1));
										break;
									case LUA_TTABLE:
										lua_getfield(L, -1, "__tostring");
										lua_call(L, 0, 1);
										typeName = luaL_checkstring (L, -1);
										lua_pop(L,1);
										switch(TypeInfo::getType(typeName))
											{
											case VECTOR3:
												params.addProperty<Ogre::Vector3>(key, *unwrap<Ogre::Vector3>(L, -1));
												break;
											case QUATERNION:
												params.addProperty<Ogre::Quaternion>(key, *unwrap<Ogre::Quaternion>(L, -1));
												break;
											default:
												break;
											}
										break;
									default:
										break;
									}

								lua_pop(L, 1);
							}
//						std::cout << params.getXmlString() << std::endl;

						go = new ::GameObject(params);

						//{{{ Transform
						go->addComponent("TransformComponent", "TransformComponentInstance");

						::Object trans = go->getProperty< ::Object >("transform");
						if(params.hasProperty("position"))
							{
								Ogre::Vector3 v = prop_cast_val<Ogre::Vector3>(params["position"]);
								prop_cast<Ogre::Vector3>(trans["pos"])->Set(v);
							}

						if(params.hasProperty("rotation"))
							prop_cast<Ogre::Quaternion>(trans.getIProperty("rotation"))->Set( prop_cast_val<Ogre::Quaternion>(params["rotation"]) );

						if(params.hasProperty("scale"))
							prop_cast<Ogre::Vector3>(trans.getIProperty("scale"))->Set( prop_cast_val<Ogre::Vector3>(params["scale"]) );
						//}}}

						//{{{ Render
						if(params.hasProperty("mesh"))
							{
								const char* mesh = prop_cast_val<const char*>(params["mesh"]);
								go->addComponent(new RenderComponent(go, "RenderComponent", mesh));

							}
						//}}}

						// Physics
						if(params.hasProperty("shape") && params.hasProperty("mass"))
							{
								go->addComponent(new PhysicsComponent(go, app->getDynWorld(), params));
							}

						if(params.hasProperty("envmap"))
							{
//								go->addComponent("EnvMapGenerator", new EnvMapGenerator(Ogre::Root::getSingletonPtr(), app->getSceneManager(), go));
							}

					}
				else
					{
						luaL_typerror(L, 1, "table");
						return 0;
					}
				break;
			case 3:
			{
				const char *mesh = luaL_checkstring(L, 1);
				const char *shapeStr = luaL_checkstring(L, 2);
				float mass = luaL_checknumber(L, 3);


				std::stringstream ss;
				ss << "Unamed_GameObject_" << Ogre::StringConverter::toString(::GameObject::howMany());
				params.addProperty<const char*>("name", ss.str().c_str());

				go = new ::GameObject(params);

				params.addProperty<const char*>("shape", shapeStr);
				params.addProperty<float>("mass", mass);

//			cout << params.getXmlString() << endl;

				go->addComponent(new TransformComponent(go));
				go->addComponent(new RenderComponent(go, mesh));
				go->addComponent(new PhysicsComponent(go, app->getDynWorld(), params));
			}

			break;

			default:
				luaL_error(L, "Incorrect number of arguments for function");
				return 0;
			}

//			lua_boxpointer(L, go);
//			luaL_getmetatable(L, className);
//			lua_setmetatable(L, -2);

		Wrapper<GameObject>::Instance(L, (void*)go);

		return 1;
	}



	int GameObject::toString (lua_State *L)
	{
		::GameObject* go = unwrap< ::GameObject>(L, 1);
		lua_pushfstring(L, "%s[%s]", go->getName().c_str(), go->getType().c_str());
		return 1;
	}

	int GameObject::toObject(lua_State *L)
	{
		::GameObject* go = unwrap< ::GameObject>(L, 1);
		Wrapper<Object>::Instance(L, (void*)(go->toObject()));
		return 1;
	}

	int GameObject::get( lua_State *L )
	{
		const char *key = luaL_checkstring(L, 2);
		if(Wrapper<GameObject>::Method(L, key)) return 1;
		return Object::get(L);
	}

	int GameObject::set( lua_State *L )
	{
		return LuaGlue::Object::set(L);
	}

	int GameObject::getByName(lua_State *L)
	{
		const char *key = luaL_checkstring(L, 1);
		::GameObject* go = ::GameObject::mObjectsByName[key];
		if(!go)
			{
				luaL_error(L, "GameObject named \"%s\" not found", key);
				return 0;
			}

		Wrapper<GameObject>::Instance(L, (void*)go);
		return 1;
	}

	int GameObject::getName(lua_State *L)
	{
//		::GameObject* go = unwrap< ::GameObject>(L, 1);
//		lua_pushstring(L, go->getName().c_str());
//		return 1;
		return Object::getName(L);
	}

	int GameObject::getObjectsTable(lua_State *L)
	{
		lua_newtable(L);

		::GameObject::ObjectIterator it = ::GameObject::getObjectsIterator();
		while(it.hasMoreElements())
			{
				::GameObject* go = it.getNext();

				lua_pushstring(L, go->getName().c_str());
				Wrapper<GameObject>::Instance(L, (void*)go);

				lua_rawset(L, -3);
			}

		return 1;
	}

	int GameObject::addComponent(lua_State *L)
	{
		int nargs = lua_gettop(L);

		::GameObject* go = unwrap< ::GameObject>(L, 1);

		const char* type = luaL_checkstring(L, 2);
		const char* name = "Unknown";

		if(nargs==3)
			name = luaL_checkstring(L, 2);

		Component* c = ComponentFactory::getSingleton().Create(go, type);
		if(c==NULL)
			{
				printf("Fatal Error: Creating Component '%s' failed\n", type);
				lua_settop(L, 0);
				return 0;
			}

		go->addComponent(c);

		return 1;
	}

}

DECLARE(GameObject)
META(GameObject)
{"__tostring", 		LuaGlue::GameObject::toString},
{"__index",   		LuaGlue::GameObject::get},
{"__newindex",		LuaGlue::GameObject::set},
{"getByName",  		LuaGlue::GameObject::getByName},
{"getObjectsTable", LuaGlue::GameObject::getObjectsTable},
METHODS(GameObject)
method(LuaGlue::GameObject, getName),
method(LuaGlue::GameObject, toObject),
method(LuaGlue::GameObject, toString),
method(LuaGlue::GameObject, addComponent),
END
